package com.abel.bigwater.dataserver.model.qdies

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.dataserver.model.PacketCmdException
import com.abel.bigwater.dataserver.model.PacketDataLenException
import com.abel.bigwater.model.JsonDateDeserializer
import com.abel.bigwater.model.JsonDateSerializer
import com.abel.bigwater.model.JsonHelper
import com.alibaba.fastjson.annotation.JSONField
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import java.util.*

/**
 * 关于历史数据上传的说明：
当到达上传时刻时， D10主动上传表计历史数据。
D10还支持主站主动读取历史数据， 主站可以指定D10上传多少条历史数据， 最多上传720条。 收
到读取485表计历史数据命令， D10立即上传485表计历史数据；
短信上传历史数据内容和GPRS上传历史内容有区别， 短信要精简， GPRS要完整。
短信历史数据上传格式： 帧头+D10地址+功能码+数据长度+内部表计地址+规约号/表类型+最后
一次抄表时刻（ 5字节） +抄表是否成功标志（ 两个字节表示， 用位表示， 置1表示抄表成功） + N
条短信版历史数据（ 每条X字节） +校验+帧尾
注： 短信版历史数据只有表计数据， 如485大表的188规约， 只上传4字节的表计数据， 而没有每
次抄表的时刻， 以及每次抄表的状态字等内容。
GPRS历史数据上传格式： 帧头+D10地址+功能码+数据长度+内部表计地址+规约号/表类型+
N条GPRS版历史数据（ 每条Y字节） +校验+帧尾
注： GPRS版历史数据内容全面， 包括每次抄表的时刻等内容。
D10要根据485表计使用的规约号进行分类， 不同的规约号使用不同长度的应答数据。
当D10到达上传时刻， 主动上传历史数据时， 485表计参数配置的模式不同， 其上传的数据格式
不同。 天模式每次上传24组历史数据， 周模式每次上传7组历史数据， 自定义模式根据上传时间
间隔（ 抄表N次后上传） 上传N组数据。
功能码 功能
0x1B 读取485表计历史数据
上位机发送： 帧头 地址 0x1B 0x03 内部表计地址 上传历史数据
的条数（ 两字节表示， 最大720条） 校验 帧尾
短信上传时， D10应答： 帧头 地址 0x1B 数据段长度 表计地址 规
约号 最后一次抄表时刻（ 年月日时分） 抄表是否成功标志（ 2字
节） N条短信版历史数据 校验 帧尾
GPRS上传时， D10应答： 帧头 地址 0x1B 数据段长度 内部表计地址
规约号 N条GPRS版历史数据 校验 帧尾
示例：
D10地址为12000001， 读取内部表计地址为1的485表计历史数据， 规约号为1， （采用天模式，
共24组数据， 每条短信上传12条历史数据， 共两条短信） 。 历史数据为100010、 100020…………。
最后一次抄表时刻12年10月25日15点0分。
帧头 地址 功能
码
数据段
长度
数据段 帧校
验
帧尾
上位机
发送
0x68 0x12 0x00
0x00 0x01
0x1B 0x01 0x01 0x** 0x16
D10应答 0x68 0x12 0x00
0x00 0x01
0x1B 0x40 0x01 0x01 0x12 0x10 0x25
0x15 0x0F 0xFF 0x47 0xC3
0x55 0x00 0x47 0xC3 0x5A
0x00…………………
0x** 0x16
注： (暂不实现)
在GPRS模式下， 当D10向主站主动上传历史数据时， D10需要判断主站是否收到历史数据， 此时主站
需应答， 主站采用统一的应答帧， 如下：
主站应答帧
帧头 地址 0xFF 0x03 内部表计地址 0x55 0xAA 校验 帧尾
示例:
D10地址为12000001， 向主站主动上传485表计历史数据， 485表计的内部表计地址为1， 主站收到历
史数据后应答。
帧头 地址 功能码 数据段
长度
数据段 帧校验 帧尾
主站应
答帧
0x68 0x12 0x00
0x00 0x01
0xFF 0x03 0x01 0x55 0xAA 0x** 0x16
 */
open class DtenAsk485HistData(_len: Int, val samplePeriod: Int = 15) : D10BaseData(_len) {
    override var type = FuncType.Ask485Hist

    var internalAddr: Byte? = null

    var readMode = ReadModeType.DayMode

    var lastReadTimeBcd: BcdString? = null

    @JsonSerialize(using = JsonDateSerializer::class)
    @JsonDeserialize(using = JsonDateDeserializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var lastReadTime: Date? = null
        get() = lastReadTimeBcd?.toDtenDate()?.toDate()

    open var readGood = BitSet(32)

    var readList: FloatArray? = null

    @JsonSerialize(using = JsonDateSerializer::class)
    @JsonDeserialize(using = JsonDateDeserializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var readTimeList: List<Date>? = null
        get() = when (readMode) {
            ReadModeType.DayMode -> List(readList!!.size) {
                DateTime(lastReadTime).minusHours(readList!!.size - it - 1).toDate()
            }

            ReadModeType.WeekMode -> List(readList!!.size) {
                DateTime(lastReadTime).minusDays(readList!!.size - it - 1).toDate()
            }

            else -> List(readList!!.size) {
                DateTime(lastReadTime).minusMinutes(samplePeriod * (readList!!.size - it - 1)).toDate()
            }
        }

    override fun fill(buf: ByteBuf) {
        assert(internalAddr != null && lastReadTimeBcd?.size == 5 && readList != null)

        buf.apply {
            writeByte(internalAddr!!.toInt())
            writeByte(readMode.code.toInt())
            writeBytes(lastReadTimeBcd!!.bcdArray)
            writeBytes(readGood.toByteArray())

            readList!!.forEach { writeFloatLE(it) }
        }
    }

    override fun parse(buf: ByteBuf): Boolean {
        if (buf.readableBytes() < MIN_LEN) {
            throw PacketDataLenException("data is not enough: ${buf.readableBytes()}")
        }

        buf.apply {
            internalAddr = readByte()
            val b1 = readByte()
            if (ReadModeType.values().find { it.code == b1 } == null) {
                throw PacketCmdException("non-exist read-mode: $b1")
            }
            readMode = ReadModeType.values().find { it.code == b1 }!!

            lastReadTimeBcd = BcdString.create(this, 5)

            readGood = BitSet.valueOf(ByteArray(2).also { readBytes(it) })
            readList = FloatArray(readableBytes().div(4))
            readList!!.forEachIndexed { index, _ -> readList!![index] = readFloatLE() }
        }

        return true
    }

    override fun count(): Int = MIN_LEN + 4 * readList!!.size

    companion object {
        const val MIN_LEN = 1 + 1 + 5 + 1

        fun BcdString.toDtenDate(): DateTime? {
            return if (this.size != 5) null
            else DateTime(this.bcdArray!![0] + DateTime.now().year / 100 * 100,
                    this.bcdArray!![1].toInt(), this.bcdArray!![2].toInt(),
                    this.bcdArray!![3].toInt(), this.bcdArray!![4].toInt())
        }

        fun BcdString.fromDtenDate(dt: DateTime): BcdString =
                BcdString(byteArrayOf(dt.year.rem(100).toByte(),
                        dt.monthOfYear.toByte(), dt.dayOfMonth.toByte(),
                        dt.hourOfDay.toByte(), dt.minuteOfHour.toByte()), true)
    }
}

/**
 * 1： 天模式（默认）
2： 周模式
3： 自定义模式
 */
enum class ReadModeType(val code: Byte) {
    DayMode(0x01),

    WeekMode(0x02),

    Custom(0x03)
}
